/* randomlib.c -- random number generation module.
 */

#include <randomlib.h>

/* Make noise buffer size be one less than power of 2 so that
 *      noise input rotation wraparound falls at the same index as least often
 *      as possible... 
 *      if too big, it will take a long time to get it properly 
 *      randomized from noise sources.
 */
#define RANDOM_NOISE_BUF_POWER 6
#define RANDOM_NOISE_BUF_SZ ((1<<RANDOM_NOISE_BUF_POWER)-1)

#define RANDOM_NOISE_WORD_SIZE 32       /* assume 32-bit cpu */
#define RANDOM_NOISE_WORD_SIZE_LOG2 5   /* log base 2 of word size */

struct randomState 
{
    unsigned rotateAmount;      /* how much to rotate input noise to */
    unsigned noiseWrIdx;          /* which of noise[] to modify */
    unsigned noiseRdIdx;          /* which of noise[] to use */
    unsigned noise[RANDOM_NOISE_BUF_SZ];
} randomS;


/* randomlibAdd -- add a word to the remembered noise state.
 *      The word should be at least somewhat noisy.
 *      Take care that there is not too much of a pattern of repetition to 
 *      how correlated words are added!!!
 *      Calling with the same data twice removes the data due to the
 *      use of xor... 
 *
 *      This should be called frequently from a variety of places ...
 */
void randomlibAdd( unsigned noise )
{
    unsigned rotateAmount = randomS.rotateAmount;
    rotateAmount = (rotateAmount + 1)&(RANDOM_NOISE_WORD_SIZE-1);
    randomS.rotateAmount = rotateAmount;
    /* Assume instruction set can shift at least 32 bits.
     *  Possible instruction set problem in propogating sign bit doesn't
     *  matter since xor won't disrupt noise.
     */
    noise = (noise << rotateAmount) ^ 
        (noise >> (RANDOM_NOISE_WORD_SIZE-rotateAmount));
    unsigned noiseWrIdx = randomS.noiseWrIdx + 1;
    if ( noiseWrIdx >= RANDOM_NOISE_BUF_SZ ) noiseWrIdx = 0;
    randomS.noiseWrIdx = noiseWrIdx;
    randomS.noise[noiseWrIdx] ^= noise;
}

/* randomlibAddEarly -- call during startup stages to give an
 *      extra head start to initializing the noise array.
 */
void randomlibAddEarly( unsigned noise )
{
    int repeat;

    for (repeat = 0; repeat < RANDOM_NOISE_BUF_SZ; repeat++) {
        randomlibAdd(noise);
        noise = randomlibGet();
    }
}

/* randomlibAddArray -- add multiple bytes of noise source
 */
void randomlibAddArray( const void *ByteArray, unsigned NBytes )
{
    int IByte;
    const unsigned char *Bytes = ByteArray;
    while ( NBytes >= sizeof(unsigned) ) {
        unsigned Sum = Bytes[0];
        for (IByte = 1; IByte < sizeof(unsigned); IByte++) {
            Sum = (Sum << 8) | Bytes[IByte];
        }
        NBytes -= sizeof(unsigned);
        randomlibAdd(Sum);
    }
    for (IByte = 0; IByte < NBytes; IByte++) {
        randomlibAdd(Bytes[IByte]);
    }
}



/* randomlibGet -- get random bits.
 *      All bits of the returned word are "random".
 */
unsigned randomlibGet(void)
{
    unsigned noiseRdIdx = randomS.noiseRdIdx + 1;
    if ( noiseRdIdx >= RANDOM_NOISE_BUF_SZ ) noiseRdIdx = 0;
    randomS.noiseRdIdx = noiseRdIdx;
    unsigned noise = randomS.noise[noiseRdIdx];
    /* Supplement this with a weak psuedo-random number generator,
     * so that we don't repeat the same random number every RANDOM_NOISE_BUF_SZ
     * times in case there is no further immediate further noise input.
     *  The following "simple"
     *  is a well known simple algorithm but is weak
     *  in the low bits; the rotation by the high bits may make up for that.
     *
     *  On the other hand, this may be overkill...
     */
    unsigned seed = noise;
    unsigned simple = (seed * 1103515245) + 12345;
    unsigned rotateAmount = (simple >> 
        (RANDOM_NOISE_WORD_SIZE-RANDOM_NOISE_WORD_SIZE_LOG2));   
    /* Assume instruction set can shift at least 32 bits.
     *  Possible instruction set problem in propogating sign bit doesn't
     *  matter since xor won't disrupt noise.
     */
    unsigned out = (simple >> rotateAmount) ^ 
        (simple << (RANDOM_NOISE_WORD_SIZE-rotateAmount));
    randomS.noise[noiseRdIdx] = out;    /* seed for next time */
    return out;
}

/* randomlibGetArray -- get multiple bytes of noise
 */
void randomlibGetArray( void *ByteArray, unsigned NBytes )
{
    unsigned char *Bytes = ByteArray;
    while ( NBytes >= sizeof(unsigned) ) {
        unsigned Word = randomlibGet();
        *Bytes++ = Word; Word >>= 8;
        *Bytes++ = Word; Word >>= 8;
        *Bytes++ = Word; Word >>= 8;
        *Bytes++ = Word; // Word >>= 8;
        NBytes -= sizeof(unsigned);
    }
    if ( NBytes > 0 ) {
        unsigned Word = randomlibGet();
        while ( NBytes > 0 ) {
            *Bytes++ = Word; Word >>= 8;
            NBytes--;
        }
    }
}

